use std::sync::Arc;

use super::user_service::UserService;
use crate::errors::cons_error;
use crate::mdb::{MClient, MDB};
use crate::models::account::GithubPlatformUserInfo;
use crate::{AppError, AppResult, Config};
use awc::Client;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use wither::bson::DateTime;

#[derive(Debug, Clone)]
pub struct GithubService {
    pub config: Arc<Config>,
    pub user_service: Arc<UserService>,
    pub mdb: MDB,
    pub m_client: MClient,
}

impl GithubService {
    pub fn new(
        config: Arc<Config>,
        user_service: Arc<UserService>,
        mdb: MDB,
        m_client: MClient,
    ) -> Self {
        Self {
            config,
            user_service,
            mdb,
            m_client,
        }
    }
    pub fn get_login_url<T: Into<String>>(&self, redirect_url: T) -> AppResult<String> {
        if let Some(client_id) = &self.config.github.client_id {
            Ok(format!(
                "{}/login/oauth/authorize?client_id={}&redirect_uri={}&scope=user",
                github_urls::GITHUB_BASE,
                client_id,
                redirect_url.into()
            ))
        } else {
            Err(AppError::ConsError(cons_error::MSG_GITHUB_NOT_CONFIG))
        }
    }
    pub async fn get_access_token_from_code(&self, code: String) -> AppResult<String> {
        if let (Some(client_id), Some(client_secret)) = (
            &self.config.github.client_id,
            &self.config.github.client_secret,
        ) {
            let client = Client::default();
            let res: Value = client
                .post(format!(
                    "{}/login/oauth/access_token?client_id={}&client_secret={}&code={}",
                    github_urls::GITHUB_BASE,
                    client_id,
                    client_secret,
                    code
                ))
                .insert_header((actix_http::header::ACCEPT, "application/json"))
                .send()
                .await
                .map_err(actix_web::error::ErrorInternalServerError)?
                .json()
                .await
                .map_err(actix_web::error::ErrorInternalServerError)?;
            if let Value::String(res) = &res["access_token"] {
                log::info!("github 成功获取 access_token: {}", res);
                return Ok(res.clone());
            }
            log::debug!("github 获取access_token 失败 {:?}", res);
            Err(AppError::other_error(res))
        } else {
            Err(AppError::ConsError(cons_error::MSG_GITHUB_NOT_CONFIG))
        }
    }
    pub async fn get_user_info_from_access_token(
        &self,
        access_token: String,
    ) -> AppResult<GithubPlatformUserInfo> {
        let client = Client::default();

        let res: GithubLoginResponse = client
            .get(format!("{}/user", github_urls::GITHUB_API_BASE))
            .insert_header((
                actix_http::header::AUTHORIZATION,
                format!("token {}", access_token),
            ))
            .insert_header((actix_http::header::ACCEPT, "application/json"))
            .insert_header((actix_http::header::USER_AGENT, "awc/3.0.0"))
            .send()
            .await
            .map_err(actix_web::error::ErrorInternalServerError)?
            .json()
            .await
            .map_err(actix_web::error::ErrorInternalServerError)?;
        Ok(GithubPlatformUserInfo::from(res))
    }
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct GithubLoginResponse {
    pub login: String,
    pub id: i64,
    pub node_id: String,
    pub avatar_url: String,
    pub gravatar_id: String,
    pub url: String,
    pub html_url: String,
    pub followers_url: String,
    pub following_url: String,
    pub gists_url: String,
    pub starred_url: String,
    pub subscriptions_url: String,
    pub organizations_url: String,
    pub repos_url: String,
    pub events_url: String,
    pub received_events_url: String,
    pub r#type: String,
    pub site_admin: bool,
    pub name: String,
    pub company: Option<String>,
    pub blog: String,
    pub location: Option<String>,
    pub email: Option<String>,
    pub hireable: Option<bool>,
    pub twitter_username: Option<String>,
    pub public_repos: i64,
    pub public_gists: i64,
    pub followers: i64,
    pub following: i64,
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub updated_at: chrono::DateTime<chrono::Utc>,
}

impl From<GithubLoginResponse> for GithubPlatformUserInfo {
    fn from(r: GithubLoginResponse) -> Self {
        Self {
            login: r.login,
            id: r.id,
            node_id: r.node_id,
            avatar_url: r.avatar_url,
            gravatar_id: r.gravatar_id,
            url: r.url,
            html_url: r.html_url,
            followers_url: r.followers_url,
            following_url: r.following_url,
            gists_url: r.gists_url,
            starred_url: r.starred_url,
            subscriptions_url: r.subscriptions_url,
            organizations_url: r.organizations_url,
            repos_url: r.repos_url,
            events_url: r.events_url,
            received_events_url: r.received_events_url,
            r#type: r.r#type,
            site_admin: r.site_admin,
            name: r.name,
            company: r.company,
            blog: r.blog,
            location: r.location,
            email: r.email,
            hireable: r.hireable,
            twitter_username: r.twitter_username,
            public_repos: r.public_repos,
            public_gists: r.public_gists,
            followers: r.followers,
            following: r.following,
            created_at: DateTime::from_chrono(r.created_at),
            updated_at: DateTime::from_chrono(r.updated_at),
        }
    }
}
mod github_urls {
    pub static GITHUB_BASE: &str = "https://github.com";
    pub static GITHUB_API_BASE: &str = "https://api.github.com";
}
